﻿#define SZW_LOADASSTREAM //getExtractor() func load using FileStream instead of FileName

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using SevenZip;
using System.IO;
using System.Text.RegularExpressions;
using COFE.Core;
using COFE.Core.Utils;
using COFE.Core.Actions;
using System.Threading;

namespace COFE.IO.SZ.Utils
{
    public class SevenZipWrapper
    {
        #region mini tuple implementation

        public class tuple<T1, T2>
        {
            public T1 Value1 { get; private set; }
            public T2 Value2 { get; private set; }
            public tuple(T1 value1, T2 value2)
            {
                Value1 = value1;
                Value2 = value2;
            }
        }

        #endregion

        #region Constructor

        public SevenZipWrapper(IInternalFileInfoExA archiveFile)
        {
            _archiveFile = archiveFile;
        }

        #endregion

        #region Static Methods


        private static OutArchiveFormat getArchiveFormat(string archiveName)
        {

            switch (PathEx.GetExtension(archiveName).ToLower())
            {
                case ".7z":
                case ".7zip": return OutArchiveFormat.SevenZip;
                case ".zip": return OutArchiveFormat.Zip;
                case ".bzip": return OutArchiveFormat.BZip2;
                case ".z":
                case ".gz":
                case ".gzip":
                    return OutArchiveFormat.GZip;
                case ".tar":
                    return OutArchiveFormat.Tar;
                default: throw new NotImplementedException();
            }
        }

        #endregion

        #region Methods

        #region getExtractor

        private SevenZipExtractor getExtractor(Stream archiveStream, string password)
        {
            if (!String.IsNullOrEmpty(password))
                return new SevenZipExtractor(archiveStream, password);
            return new SevenZipExtractor(archiveStream);
        }

        private SevenZipExtractor getExtractor(string archivePath, string password)
        {
            if (!String.IsNullOrEmpty(password))
                return new SevenZipExtractor(archivePath, password);
            return new SevenZipExtractor(archivePath);
        }

#if SZW_LOADASSTREAM

        internal SevenZipExtractor getExtractor(string password)
        {
            try
            {
                MemoryStream ms = new MemoryStream();
                using (Stream fs = _archiveFile.OpenRead())
                    StreamUtils.CopyStream(fs, ms);
                return getExtractor(ms, password);
            }
            catch
            {
                return getExtractor(_archiveFile.FullName, password);
            }
        }

        internal SevenZipExtractor getExtractor()
        {
            lock (_globalLockObject)
            {
                try
                {
                    MemoryStream ms = new MemoryStream();
                    using (Stream fs = _archiveFile.OpenRead())
                        StreamUtils.CopyStream(fs, ms);
                    return getExtractor(ms, null);
                }
                catch
                {
                    return getExtractor(_archiveFile.FullName, null);
                }
            }
        }


#else
        internal SevenZipExtractor getExtractor(string password)
        {
            return getExtractor(_archiveFile.FullName, password);
        }
        internal SevenZipExtractor getExtractor()
        {
            return getExtractor(_archiveFile.FullName, null);
        }
#endif
        #endregion

        #region getCompressor

        private SevenZipCompressor getCompressor(string archivePath, string password)
        {
            SevenZipCompressor compressor = new SevenZipCompressor();
            compressor.DirectoryStructure = true;
            compressor.ArchiveFormat = getArchiveFormat(archivePath);
            compressor.CompressionMode = CompressionMode.Append;
            return compressor;
        }

        internal SevenZipCompressor getCompressor(string password)
        {
            return getCompressor(_archiveFile.FullName, password);
        }

        internal SevenZipCompressor getCompressor()
        {
            lock (_globalLockObject)
            {
                return getCompressor(_archiveFile.FullName, "");
            }
        }

        #endregion


        public IEnumerable<tuple<int, string>> lookup(string pathOrMask)
        {
            foreach (ArchiveFileInfo afi in getExtractor().ArchiveFileData)
                if (IOTools.MatchFileMask(afi.FileName, pathOrMask))
                {
                    yield return new tuple<int, string>(afi.Index, afi.FileName);
                }
        }


        /// <summary>
        /// List entries that match the filterStr, the filterStr must be in format of RegexPatterns.SevenZipListPattern.
        /// </summary>
        /// <param name="filterStr"></param>
        /// <returns></returns>
        public IEnumerable<ArchiveFileInfo> list(string pattern, bool listSubdir = false)
        {
            if (pattern.IndexOf("?<name>") == -1)
                throw new ArgumentException("");
            if (pattern.IndexOf("?<trail>") == -1)
                throw new ArgumentException("");
            if (pattern.IndexOf("?<parent>") == -1)
                throw new ArgumentException("");

            List<string> returnedPathList = new List<string>();

            
            lock (_lockObject)
            {
                List<ArchiveFileInfo> afiList;

                try
                {
                    using (SevenZipExtractor extractor = getExtractor())
                    {
                        afiList = new List<ArchiveFileInfo>(extractor.ArchiveFileData);
                    }
                }
                catch { afiList = new List<ArchiveFileInfo>(); }

                foreach (ArchiveFileInfo afi in afiList)
                {
                    Match match = new Regex(pattern).Match(afi.FileName);
                    if (match.Success)
                    {
                        string parent = match.Groups["parent"].Value;
                        string name = match.Groups["name"].Value;
                        string trail = match.Groups["trail"].Value;

                        if (!afi.IsDirectory && String.IsNullOrEmpty(trail))
                        {
                            if (returnedPathList.IndexOf(afi.FileName.ToLower()) == -1)
                            {
                                yield return afi;
                                returnedPathList.Add(afi.FileName.ToLower());
                            }
                        }
                        else if (!String.IsNullOrEmpty(trail) || listSubdir)
                        {
                            string dirName = PathEx.Combine(parent, name);
                            if (returnedPathList.IndexOf(dirName.ToLower()) == -1)
                            {
                                yield return new ArchiveFileInfo()
                                {
                                    FileName = dirName,
                                    IsDirectory = true
                                };
                                returnedPathList.Add(dirName.ToLower());
                            }
                        }
                    }
                }
            }
        }


        public IEnumerable<ArchiveFileInfo> listSimple(string relativePathOrMask)
        {
            lock (_lockObject)
                using (SevenZipExtractor extractor = getExtractor())
                    lock (extractor)
                    {
                        foreach (ArchiveFileInfo afi in extractor.ArchiveFileData)
                        {
                            if (IOTools.MatchFileMask(afi.FileName, relativePathOrMask, true))
                                yield return afi;

                        }
                    }
        }



        public bool test(SevenZipExtractor extractor)
        {
            return extractor.Check();
        }

        public bool exists(string pathOrMask, bool isFolder)
        {
            lock (_lockObject)
                using (SevenZipExtractor extractor = getExtractor())
                {
                    foreach (ArchiveFileInfo afi in extractor.ArchiveFileData)
                    {
                        if (IOTools.MatchFileMask(afi.FileName, pathOrMask) && (afi.IsDirectory == isFolder))
                            return true;
                        else if (afi.FileName.StartsWith(pathOrMask, StringComparison.InvariantCultureIgnoreCase))
                            return true;
                    }
                }
            return false;
        }


        #region Extract

        private void extract(SevenZipExtractor extractor, string mask, string toDir, string password)
        {
            if (extractor.FilesCount == 0)
                return;

            if (!extractor.IsSolid)
            {
                ExtractFileCallback callBack = (t) =>
                {
                    if (IOTools.MatchFileMask(t.ArchiveFileInfo.FileName, mask))
                    {
                        if (t.Reason == ExtractFileCallbackReason.Start)
                        {
                            string outputFileName = PathEx.Combine(toDir, t.ArchiveFileInfo.FileName);
                            string outputDir = PathEx.GetDirectoryName(outputFileName);

                            if (!Directory.Exists(outputDir))
                                Directory.CreateDirectory(outputDir);

                            if (t.ArchiveFileInfo.IsDirectory)
                            {
                                if (!Directory.Exists(outputFileName))
                                    Directory.CreateDirectory(outputFileName);
                            }
                            else
                            {
                                if (File.Exists(outputFileName))
                                    File.Delete(outputFileName);

                                t.ExtractToFile = outputFileName;
                            }
                        }
                    }
                };

                extractor.ExtractFiles(callBack);
            }
            else
            {
                List<int> indexes = new List<int>();
                foreach (var afi in extractor.ArchiveFileData)
                    if (IOTools.MatchFileMask(afi.FileName, mask))
                        indexes.Add(afi.Index);
                extractor.ExtractFiles(toDir, indexes.ToArray());


            }

        }

        public void extract(string mask, string toDir, string password,
            EventHandler<FileInfoEventArgs> onExtractionStarted,
            EventHandler<FileInfoEventArgs> onExtractionEnded,
            EventHandler<ProgressEventArgs> onExtracting)
        {
            using (SevenZipExtractor extractor = getExtractor(password))
            {
                if (onExtractionStarted != null) extractor.FileExtractionStarted += onExtractionStarted;
                if (onExtractionEnded != null) extractor.FileExtractionStarted += onExtractionEnded;
                if (onExtracting != null) extractor.Extracting += onExtracting;

                extract(extractor, mask, toDir, password);

                if (onExtractionStarted != null) extractor.FileExtractionStarted -= onExtractionStarted;
                if (onExtractionEnded != null) extractor.FileExtractionStarted -= onExtractionEnded;
                if (onExtracting != null) extractor.Extracting -= onExtracting;


            }
        }

        public void extract(string mask, string toDir, string password)
        {
            extract(mask, toDir, password, null, null, null);
        }
        #endregion

        #region Compress

        public void compressOne(string fileFullPath, string archiveRelativePath)
        {
            SevenZipCompressor compressor = getCompressor();

            if (File.Exists(this._archiveFile.FullName))
            {
                compressor.CompressionMode = CompressionMode.Append;
            }

            Dictionary<string, string> fileDictionary = new Dictionary<string, string>();

            string name = PathEx.GetFileName(fileFullPath);
            fileDictionary.Add(PathEx.Combine(archiveRelativePath, name), fileFullPath);

            if (compressor.CompressionMode == CompressionMode.Append)
                if (exists(PathEx.Combine(archiveRelativePath, name), false))
                    delete(PathEx.Combine(archiveRelativePath, name));

            lock (_lockObject)
                compressor.CompressFileDictionary(fileDictionary, _archiveFile.FullName);

        }

        public void compressMultiple(Dictionary<string, Stream> streamDic, IWorkProgress workProgress)
        {
            SevenZipCompressor compressor = getCompressor();

            if (File.Exists(this._archiveFile.FullName))
            {
                compressor.CompressionMode = CompressionMode.Append;
            }

            //compressor.Compressing += (o, e) =>
            //    {
            //        if (!workProgress.ReportProgress(e.PercentDone, 100))
            //            e.Cancel = true;                   
            //    };

            compressor.FileCompressionStarted += (o, e) =>
                {
                    if (!workProgress.ReportProgress(e.PercentDone, 100, e.FileName))
                        e.Cancel = true;
                    workProgress.CheckPause();
                };

            lock (_lockObject)
                compressor.CompressStreamDictionary(streamDic, _archiveFile.FullName);
        }

        #endregion

        #region Delete

        public void delete(string delPathOrMask)
        {

            Dictionary<int, string> fileDictionary = new Dictionary<int, string>();

            foreach (var foundItem in lookup(delPathOrMask))
                fileDictionary.Add(foundItem.Value1, null);

            if (fileDictionary.Count > 0)
            {
                lock (_lockObject)
                {
                    SevenZipCompressor compressor = getCompressor();
                    compressor.ModifyArchive(_archiveFile.FullName, fileDictionary);
                }
            }
        }

        #endregion

        #endregion

        #region Data

        private IInternalFileInfoExA _archiveFile;        
        private object _lockObject = new object();
        private static object _globalLockObject = new object();

        #endregion

        #region Public Properties

        public IInternalFileInfoExA ArchiveFile { get { return _archiveFile; } }


        #endregion


    }

}
